package com.xiang.ad.handler;

import com.alibaba.fastjson.JSON;
import com.xiang.ad.dump.table.AdCreativeTable;
import com.xiang.ad.dump.table.AdCreativeUnitTable;
import com.xiang.ad.dump.table.AdPlanTable;
import com.xiang.ad.dump.table.AdUnitDistrictTable;
import com.xiang.ad.dump.table.AdUnitItTable;
import com.xiang.ad.dump.table.AdUnitKeywordTable;
import com.xiang.ad.dump.table.AdUnitTable;
import com.xiang.ad.index.DataTable;
import com.xiang.ad.index.IndexAware;
import com.xiang.ad.index.adplan.AdPlanIndex;
import com.xiang.ad.index.adplan.AdPlanObject;
import com.xiang.ad.index.adunit.AdUnitIndex;
import com.xiang.ad.index.adunit.AdUnitObject;
import com.xiang.ad.index.creative.CreativeIndex;
import com.xiang.ad.index.creative.CreativeObject;
import com.xiang.ad.index.creativeunit.CreativeUnitIndex;
import com.xiang.ad.index.creativeunit.CreativeUnitObject;
import com.xiang.ad.index.district.UnitDistrictIndex;
import com.xiang.ad.index.interest.UnitItIndex;
import com.xiang.ad.index.keyword.UnitKeywordIndex;
import com.xiang.ad.mysql.constant.OpType;
import com.xiang.ad.utils.CommonUtils;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * 实现增量数据的更新（根据不同的操作）
 *
 * 1. 索引之间存在着层级的划分, 也就是依赖关系的划分
 * 2. 加载全量索引其实是增量索引 "添加" 的一种特殊实现，好处就是 可以实现全量和增量同时的更新  共同的构造过程
 * level_1是用户层级，这里不需要，所以可以从第二层级开始设计
 * 所谓的依赖关系并不是说 index 之间的依赖，而是数据表之间的依赖
 * Created by xiang.
 */
@Slf4j
public class AdLevelDataHandler {

    /**
     * @param planTable   导出的数据文件 table
     * @param type        指定操作类型
     * 将数据对象xxxTable根据不同的操作类型去影响（构造、改变）到当前系统中的index
     *
     * 1、第二层级代表着：不与其他层级存在着关联关系，不依赖于其他的索引就可以构建
     * 2、第三层级代表着：依赖于第二层级的plan，并且与自身（第三层级）相关联
     * 3、第四层级代表着：三个维度的限制都与第三层级的unit有依赖关系
     */


    //2级索引-Plan
    public static void handleLevel2(AdPlanTable planTable,//全量索引导入到文件中是json数据，就是xxxtable
                                    OpType type) {

        //将planTable（导出的文件）转化为planObject（索引实体）
        AdPlanObject planObject = new AdPlanObject(
                //填充属性
                planTable.getId(),
                planTable.getUserId(),
                planTable.getPlanStatus(),
                planTable.getStartDate(),
                planTable.getEndDate()
        );
        //
        handleBinlogEvent(
                DataTable.of(AdPlanIndex.class),//通过类名可以获取到对应的index的服务
                planObject.getPlanId(),//key是plan的id
                planObject, //value是index的实体
                type //操作类型
        );
    }

    //2级索引-Creative
    public static void handleLevel2(AdCreativeTable creativeTable,
                                    OpType type) {
        //将 creativeTable 转化为 CreativeObject
        CreativeObject creativeObject = new CreativeObject(
                creativeTable.getAdId(),                creativeTable.getName(),
                creativeTable.getType(),
                creativeTable.getMaterialType(),
                creativeTable.getHeight(),
                creativeTable.getWidth(),
                creativeTable.getAuditStatus(),
                creativeTable.getAdUrl()
        );
        //对CreativeObject进行索引操作
        handleBinlogEvent(
                DataTable.of(CreativeIndex.class),//通过类型对应操作index
                creativeObject.getAdId(),
                creativeObject,
                type
        );
    }

    //3级索引-adUnit   unitTable中有planId，依赖于plan构造
    public static void handleLevel3(AdUnitTable unitTable,
                                    OpType type) {

        AdPlanObject adPlanObject = DataTable.of(
                AdPlanIndex.class
        ).get(unitTable.getPlanId());
        if (null == adPlanObject) { //如果推广计划还未建立，就无法创建unit，这就是依赖限制
            log.error("handleLevel3 found AdPlanObject error: {}",
                    unitTable.getPlanId());
            return;
        }
        //unitTable转化为AdUnitObject
        AdUnitObject unitObject = new AdUnitObject(
                unitTable.getUnitId(),
                unitTable.getUnitStatus(),
                unitTable.getPositionType(),
                unitTable.getPlanId(),
                adPlanObject
        );
        //管理索引服务
        handleBinlogEvent(
                DataTable.of(AdUnitIndex.class),//通过缓存获取index服务
                unitTable.getUnitId(), //key是unitId
                unitObject, //value是unit index的实体
                type //操作类型
        );
    }

    //3级索引-creativeUnit  依赖于creative本身，也会依赖于unit
    public static void handleLevel3(AdCreativeUnitTable creativeUnitTable,
                                    OpType type) {
        //因为不支持更新，判断是否是更新操作，是的话直接返回了（拒绝）
        if (type == OpType.UPDATE) {
            log.error("CreativeUnitIndex not support update");
            return;
        }
        //获取 推广单元index对象
        AdUnitObject unitObject = DataTable.of(
                AdUnitIndex.class
        ).get(creativeUnitTable.getUnitId());
        //获取 创意 index对象
        CreativeObject creativeObject = DataTable.of(
                CreativeIndex.class
        ).get(creativeUnitTable.getAdId());
        //如果 unit和creative其中的任意一个index对象为空
        if (null == unitObject || null == creativeObject) {
            log.error("AdCreativeUnitTable index error: {}",
                    JSON.toJSONString(creativeUnitTable));//打印json形式的日志
            return;
        }
        //准备齐全就可以构建第三层级索引了
        CreativeUnitObject creativeUnitObject = new CreativeUnitObject(
                creativeUnitTable.getAdId(), //填充adId
                creativeUnitTable.getUnitId() //填充UnitId
        );
        //管理索引服务
        handleBinlogEvent(
                DataTable.of(CreativeUnitIndex.class),//通过类型获取索引服务
                //由于CreativeUnitIndex的key是一个string类型，adId-unitId合并在一起中间有个连接符
                CommonUtils.stringConcat( //将adId和unitId连接起来  作为key
                        creativeUnitObject.getAdId().toString(),
                        creativeUnitObject.getUnitId().toString()
                ),
                creativeUnitObject, //value
                type  //index操作类型
        );
    }


    //4级索引-关键词限制
    public static void handleLevel4(AdUnitKeywordTable keywordTable,
                                    OpType type) {
        //不支持更新
        if (type == OpType.UPDATE) {
            log.error("keyword index can not support update");
            return;
        }
        //尝试获取unit的index实体类对象
        AdUnitObject unitObject = DataTable.of(
                AdUnitIndex.class
        ).get(keywordTable.getUnitId());
        if (unitObject == null) {
            log.error("AdUnitKeywordTable index error: {}",
                    keywordTable.getUnitId());
            return;
        }
        //通过singleton获取只有一个元素的set，作为value
        Set<Long> value = new HashSet<>(
                Collections.singleton(keywordTable.getUnitId())
        );
        //管理index的服务
        handleBinlogEvent(
                DataTable.of(UnitKeywordIndex.class),//通过类型获取index的服务
                keywordTable.getKeyword(), //关键词作为key
                value,
                type   //index的操作类型
        );
    }
    //4级索引-地域限制
    public static void handleLevel4(AdUnitDistrictTable unitDistrictTable,
                                    OpType type) {
        //不支持更新
        if (type == OpType.UPDATE) {
            log.error("district index can not support update");
            return;
        }
        //尝试通过传入的unitDistrictTable获取推广单元index实体
        AdUnitObject unitObject = DataTable.of(
                AdUnitIndex.class
        ).get(unitDistrictTable.getUnitId());
        if (unitObject == null) {//没有获取到依赖的推广单元
            log.error("AdUnitDistrictTable index error: {}",
                    unitDistrictTable.getUnitId());
            return;
        }
        //将省和市拼接起来，作为string类型的key
        String key = CommonUtils.stringConcat(
                unitDistrictTable.getProvince(),
                unitDistrictTable.getCity()
        );
        //value是Long类型的set
        Set<Long> value = new HashSet<>(
                Collections.singleton(unitDistrictTable.getUnitId()) //构造只有一个元素的set集合
        );
        //管理索引服务
        handleBinlogEvent(
                DataTable.of(UnitDistrictIndex.class),//通过类型 获取对应的index服务
                key,
                value,
                type //index操作类型
        );
    }
    //4级索引-兴趣限制
    public static void handleLevel4(AdUnitItTable unitItTable,
                                    OpType type) {
        //不支持更新
        if (type == OpType.UPDATE) {
            log.error("it index can not support update");
            return;
        }
        //尝试获取unit的index实体对象
        AdUnitObject unitObject = DataTable.of(
                AdUnitIndex.class
        ).get(unitItTable.getUnitId());
        if (unitObject == null) {
            log.error("AdUnitItTable index error: {}",
                    unitItTable.getUnitId());
            return;
        }
        //获取value  就是unitId的set
        Set<Long> value = new HashSet<>(
                Collections.singleton(unitItTable.getUnitId()) //通过singleton构造只有一个元素的set
        );
        //管理index服务
        handleBinlogEvent(
                DataTable.of(UnitItIndex.class),//通过类型获取对应index的服务
                unitItTable.getItTag(), //itTag作为key  string类型的
                value,
                type //操作类型
        );
    }



    //同时用到全量索引和增量索引的更新过程
    //监听 binlog事件（这里设计全量索引可以作为"特殊的增量索引"，可以 同时处理全量索引和增量索引）
    //1、监听binlog构造增量索引
    //2、也可以实现全量索引
    private static <K, V> void handleBinlogEvent(
            IndexAware<K, V> index, //index的crud接口
            K key,  //
            V value, //
            OpType type) { //标记当前对index是何种操作，crud的哪一个

        switch (type) {
            case ADD: //添加
                index.add(key, value);
                break;
            case UPDATE://更新
                index.update(key, value);
                break;
            case DELETE://删除
                index.delete(key, value);
                break;
            default:
                break;
        }
    }
}
